import charts.Factory;
import charts.ObjectCollection;
import charts.series.Element;
import charts.series.HasTooltip;
import com.adobe.images.JPGEncoder;
import com.adobe.images.PNGEncoder;
import elements.Background;
import elements.axis.RadarAxis;
import elements.axis.XAxis;
import elements.axis.XAxisLabels;
import elements.axis.YAxisBase;
import elements.axis.YAxisLeft;
import elements.axis.YAxisRight;
import elements.labels.Keys;
import elements.labels.Title;
import elements.labels.XLegend;
import elements.labels.YLegendBase;
import elements.labels.YLegendLeft;
import elements.labels.YLegendRight;
import elements.menu.Menu;
import flash.display.BitmapData;
//import flash.display.LoaderInfo; // TODO - check if this is necessary or flash.Lib.current.loaderInfo suffices.
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.ContextMenuEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.external.ExternalInterface;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.net.URLRequestHeader;
import flash.net.URLRequestMethod;
import flash.net.URLVariables;
import flash.system.System;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
import flash.utils.ByteArray;
import global.Global;
import hxjson2.JSON;
import mx.utils.Base64Encoder;
import string.Utils;
//import flash.net.navigateToURL; // NOTE: useless because -> http://markmail.org/message/5ancxiqekxwu6jq4
//import flash.Lib.current;
//import Reflect;


class Main extends Sprite {


	// var x_labels:XAxisLabels;
	var x_axis:XAxis;
	var radar_axis:RadarAxis;
	var x_legend:XLegend;
	var y_axis:YAxisBase;
	var y_axis_right:YAxisBase;
	var y_legend:YLegendBase;
	var y_legend_2:YLegendBase;
	var keys:Keys;
	var object_collection:ObjectCollection;
	public var tool_tip_wrapper:String;
	var screen_coords:ScreenCoords;
	var tooltip:Tooltip;
	var background:Background;
	var ok:Bool;
	var URL:String;		// ugh, vile. The IOError doesn't report the URL
	var id:String;
	var chart_parameters:Dynamic;
	var menu:Menu;

	public static var VERSION:String = "1 Mana";
	public static var title:Title = null;

	
	public function new() {
		super();
		var _loaderInfo = flash.Lib.current.loaderInfo; // TODO - check if this is a good replacement for LoaderInfo()...
		this.chart_parameters = _loaderInfo.parameters;
		if( Reflect.hasField(chart_parameters,'loading') )
			Reflect.setField(chart_parameters, 'loading', 'Loading data...');
			
		var l:Loading = new Loading(Reflect.field(chart_parameters,'loading'));
		this.addChild( l );

		this.build_right_click_menu();
		this.ok = false;

		var file:String = '';
		if( !this.find_data() )
		{
			// no data found -- debug mode?
			try {
				file = "../../data-files/menu.txt";
				//var file:String = "../../data-files/radar-2.txt";
				//var file:String = "../../../test-data-files/stack.txt";
				this.load_external_file( file );

				/*
				// test AJAX calls like this:
				var file:String = "../data-files/bar-2.txt";
				this.load_external_file( file );
				file = "../data-files/radar-area.txt";
				this.load_external_file( file );
				*/
			}
			catch (e:Dynamic) {
				this.show_error( 'Loading test data\n'+file+'\n'+e.message );
			}
		}
		
		// inform javascreen_coordsript that it can call our reload method
		ExternalInterface.addCallback("reload", reload); // mf 18nov08, line 110 of original 'main.as'
		
		// inform javascreen_coordsript that it can call our load method
		ExternalInterface.addCallback("load", load);
		
		// inform javascreen_coordsript that it can call our post_image method
		ExternalInterface.addCallback("post_image", post_image);
		
		// 
		ExternalInterface.addCallback("get_img_binary",  getImgBinary);

		// more Interface
		ExternalInterface.addCallback("get_version",	getVersion);
		
		// tell the web page that we are ready
		if( Reflect.hasField(chart_parameters,'id') )
			ExternalInterface.call("ofc_ready", Reflect.field(chart_parameters,'id'));
		else
			ExternalInterface.call("ofc_ready");
		
		this.set_the_stage();
	}

	public static function main() {		
		new Main();
	}
	
	public function getVersion():String {return VERSION;}
	
	// public function getImgBinary():String { return Base64.encodeByteArray(image_binary()); }
	public function getImgBinary():String {
		
		//tr.ace('Saving image :: image_binary()');

		var s = flash.Lib.current.stage;

		var bmp:BitmapData = new BitmapData(s.stageWidth, s.stageHeight);
		bmp.draw(this);
		
		var b64:Base64Encoder = new Base64Encoder();
		
		var b:ByteArray = PNGEncoder.encode(bmp);
		
		// var encoder:JPGEncoder = new JPGEncoder(80);
		// var q:ByteArray = encoder.encode(bmp);
		// b64.encodeBytes(q);
		
		//
		//
		//
		b64.encodeBytes(b);
		return b64.toString();
		//
		// commented out by J vander? why?
		// return b64.flush();
		//
		//
		
		
		/*
		var b64:Base64Encoder = new Base64Encoder();
		b64.encodeBytes(image_binary());
		//tr.ace( cast(b64,String));
		return b64 as String;
		*/
	}
	
	
	/**
		* Called from the context menu:
		*/
	public function saveImage(e:ContextMenuEvent):Void {
		// ExternalInterface.call("save_image", Reflect.field(chart_parameters,'id'));// , getImgBinary());
		// ExternalInterface.call("save_image", getImgBinary());
		
		// this just calls the javascreen_coordsript function which will grab an image from use
		// an do something with it.
		ExternalInterface.call("save_image", Reflect.field(chart_parameters,'id'));
	}

	
	function image_binary() : ByteArray {
		//tr.ace('Saving image :: image_binary()');

		var pngSource:BitmapData = new BitmapData(Std.int(this.width), Std.int(this.height));
		pngSource.draw(this);
		return PNGEncoder.encode(pngSource);
	}

	//
	// External Interface called by Javascreen_coordsript to
	// save the cast(flash,an)image, then POST it to a URL
	//
	//public function post_image(url:String, post_params:Dynamic, callback_str:String, debug:Bool):Void {
	public function post_image(url:String, post_params:Array<String>, callback_str:String, debug:Bool):Void { // TODO - figure out the type of post_params then change it
	
		var header:URLRequestHeader = new URLRequestHeader("Content-type", "application/octet-stream");

		//Make sure to use the correct path to jpg_encoder_download.php
		var request:URLRequest = new URLRequest(url);
		
		request.requestHeaders.push(header);
		request.method = URLRequestMethod.POST;
		//
		request.data = image_binary();

		var loader:URLLoader = new URLLoader();
		loader.dataFormat = URLLoaderDataFormat.VARIABLES;
	
		/*
		 * i can't figure out how to make these work
		 * 
		//var urlVars:URLVariables = new URLVariables();
		// NOTE: This code will work under the assumption that post_params of the Array<String> type
		//for ( key in post_params) {
		//	urlVars[key] = post_params[key];
		//}
		*/
		// base64:
		// urlVars.b64_image_data =  getImgBinary();
		// RAW:
		// urlVars.b64_image_data = image_binary();
		
		// request.data = urlVars;

		var id:String = '';
		if ( Reflect.hasField(chart_parameters,'id') )
			id = Reflect.field(chart_parameters,'id');
			
		if( debug )
		{
			// debug the PHP:
			//flash.net.navigateToURL(request, "_blank");
			var target : String = "_blank";
#if flash8
			flash.Lib.getURL (url, target);
#elseif flash9
			flash.Lib.getURL (request, target);
#end 
		}
		else
		{
			//we have to use the PROGRESS event instead of the COMPLETE event due to a bug in flash
			loader.addEventListener(ProgressEvent.PROGRESS, function (e:ProgressEvent):Void {
				
					//tr.ace("progress:" + e.bytesLoaded + ", total: " + e.bytesTotal);
					if ((e.bytesLoaded == e.bytesTotal) && (callback_str != null)) {
						//tr.aces('Calling: ', callback_str + '(' + id + ')'); 
						ExternalInterface.call(callback_str, id);
					}
				});

			try {
				loader.load( request );
			} catch (e:Dynamic) {
				//tr.ace("unable to load:" + error);
			}
			
			/*
			var loader:URLLoader = new URLLoader();
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, function(e:Event):Void {
				//tr.ace('Saved image to:');
				//tr.ace( url );
				//
				// when the upload has finished call the user
				// defined javascreen_coordsript function/method
				//
				ExternalInterface.call(callback_str);
				});
				
			loader.load( jpgURLRequest );
			*/
		}
	}

	
	function onContextMenuHandler(event:ContextMenuEvent):Void
	{
	}
	
	//
	// try to find some data to load,
	// check the URL for a file name,
	//
	//
	public function find_data(): Bool {
					
		// var all:String = ExternalInterface.call("window.location.href.toString");
		var vars:String = ExternalInterface.call("window.location.search.substring", 1);
		
		if( vars != null )
		{
			var p:Array<String> = vars.split( '&' );
			for ( v in p )
			{
				if( v.indexOf( 'ofc=' ) > -1 )
				{
					var tmp:Array<String> = v.split('=');
					//tr.ace( 'Found external file:' + tmp[1] );
					this.load_external_file( tmp[1] );
					//
					// LOOK:
					//
					return true;
				}
			}
		}
		
		if( Reflect.hasField(chart_parameters,'data-file') )
		{
			// //tr.ace( 'Found parameter:' + parameters['data-file'] );
			this.load_external_file( Reflect.field(chart_parameters,'data-file') );
			//
			// LOOK:
			//
			return true;
			
		}
		
		var get_data:String = 'open_flash_chart_data';
		if( Reflect.hasField(chart_parameters,'get-data') )
			get_data = Reflect.field(this.chart_parameters,'get-data');
		
		var json_string:Dynamic;
		
		if( Reflect.hasField(chart_parameters,'id') )
			json_string = ExternalInterface.call( get_data , Reflect.field(chart_parameters,'id'));
		else
			json_string = ExternalInterface.call( get_data );
		
		
		if( json_string != null && Std.is(json_string, String) )
		{
			parse_json( json_string );
			
			//
			// We have loaded the data, so this.ok = true
			//
			ok = true;
			//
			// LOOK:
			//
			return true;
		}
		return false;
	}
	
	
	//
	// an external Interface, used by javascreen_coordsript to
	// reload JSON from a URL :: mf 18nov08
	//
	public function reload( url:String ):Void {

		var l:Loading = new Loading(Reflect.field(chart_parameters,'loading'));
		this.addChild( l );
		this.load_external_file( url );
	}


	function load_external_file( file:String ):Void {
		
		this.URL = file;
		//
		// LOAD THE DATA
		//
		var loader:URLLoader = new URLLoader();
		loader.addEventListener( IOErrorEvent.IO_ERROR, this.ioError );
		loader.addEventListener( Event.COMPLETE, xmlLoaded );
		
		var request:URLRequest = new URLRequest(file);
		loader.load(request);
	}
	
	function ioError( e:IOErrorEvent ):Void {
		
		// remove the 'loading data...' msg:
		this.removeChildAt(0);
		var msg:ErrorMsg = new ErrorMsg( 'Open HaXe Chart\nIO ERROR\nLoading test data\n' + e.text );
		msg.add_html('This,the URL that I tried to open:<br><a href="'+this.URL+'">'+this.URL+'</a>');
		this.addChild( msg );
	}
	
	function show_error( msg:String ):Void {
		
		// remove the 'loading data...' msg:
		this.removeChildAt(0);

		var m:ErrorMsg = new ErrorMsg( msg );
		//m.add_html( 'Click here to open your JSON file: <a href="http://a.com">asd</a>' );
		this.addChild(m);
	}

	public function get_x_legend() : XLegend {
		return this.x_legend;
	}
	
	function set_the_stage():Void {
		var s = flash.Lib.current.stage;

		// tell flash to align top left, and not to screen_coordsale
		// anything (we do that in the code)
		s.align = StageAlign.TOP_LEFT;
		//
		// ----- RESIZE ----
		//
		// noScale: now we can pick up resize events
		//s.screen_coordsaleMode = StageScaleMode.NO_SCALE;
                s.scaleMode = StageScaleMode.NO_SCALE;
		s.addEventListener(Event.ACTIVATE, this.activateHandler);
		s.addEventListener(Event.RESIZE, this.resizeHandler);
		s.addEventListener(Event.MOUSE_LEAVE, this.mouseOut);
		
		this.addEventListener( MouseEvent.MOUSE_OVER, this.mouseMove );
	}
	
	
	function mouseMove( event:Event ):Void {
		// //tr.ace( 'over ' + event.target );
		// //tr.ace('move ' + Math.random().toString());
		// //tr.ace( this.tooltip.get_tip_style() );
		
		switch( this.tooltip.get_tip_style() ) {
			case Tooltip.CLOSEST:
				this.mouse_move_closest( event );
				
			case Tooltip.PROXIMITY:
				this.mouse_move_proximity( cast(event, MouseEvent) );
				
			case Tooltip.NORMAL:
				this.mouse_move_follow( cast(event, MouseEvent) );
				
		}
	}
	
	function mouse_move_follow( event:MouseEvent ):Void {

		// //tr.ace( event.currentTarget );
		// //tr.ace( event.target );
		
		if (Std.is(event.target,HasTooltip ))
			this.tooltip.draw( cast(event.target,HasTooltip));
		else
			this.tooltip.hide();
	}
	
	function mouse_move_proximity( event:MouseEvent ):Void {

		////tr.ace( event.currentTarget );
		////tr.ace( event.target );
		
		var elements:Array<HasTooltip> = this.object_collection.mouse_move_proximity( this.mouseX, this.mouseY );
		this.tooltip.closest( elements ); // TODO - change this to Element or HasTooltip
	}
	
	function mouse_move_closest( event:Event ):Void {
		
		var elements:Array<HasTooltip> = this.object_collection.closest_2( this.mouseX, this.mouseY );
		this.tooltip.closest( elements ); // TODO - change this to Element or HasTooltip
	}
	
	function activateHandler(event:Event):Void {
		//tr.ace("activateHandler: " + event);
	}

	function resizeHandler(event:Event):Void {
		// FlashConnect.trace("resizeHandler: " + event);
		resize();
	}
	
	//
	// pie charts are simpler to resize, they don't
	// have all the extras (X,Y axis, legends etc..)
	//
	function resize_pie(): ScreenCoordsBase {	
		// should this be here?
		this.addEventListener(MouseEvent.MOUSE_MOVE, this.mouseMove);
		
		this.background.resize();
		Main.title.resize();
		
		// this object is used in the mouseMove method
		this.screen_coords = new ScreenCoords(
			Main.title.get_height(), 0, this.stage.stageWidth, this.stage.stageHeight,
			null, null, null, 0, 0, false );
		this.object_collection.resize( screen_coords );
		
		return screen_coords;
	}
	
	//
	//
	function resize_radar(): ScreenCoordsBase {
		
		this.addEventListener(MouseEvent.MOUSE_MOVE, this.mouseMove);
		
		this.background.resize();
		Main.title.resize();
		this.keys.resize( 0, Main.title.get_height() );
			
		var top: Float = Main.title.get_height() + this.keys.get_height();
		
		// this object is used in the mouseMove method
		var screen_coords:ScreenCoordsRadar = new ScreenCoordsRadar(top, 0, this.stage.stageWidth, this.stage.stageHeight);
		
		screen_coords.set_range( this.radar_axis.get_range() );
		// 0-4 = 5 spokes
		screen_coords.set_angles( this.object_collection.get_max_x()-this.object_collection.get_min_x()+1 );
		
		// resize the axis first because they may
		// change the radius (to fit the labels on screen_coordsreen)
		this.radar_axis.resize( screen_coords );
		this.object_collection.resize( screen_coords );
		
		return screen_coords;
	}
	
	function resize():Void {
		//
		// the chart is async, so we may get this
		// event before the chart has loaded, or has
		// partly loaded
		//
		if ( !this.ok )
			return;			// <-- something is wrong
	
		var screen_coords:ScreenCoordsBase;
		
		if ( this.radar_axis != null )
			screen_coords = this.resize_radar();
		else if ( this.object_collection.has_pie() )
			screen_coords = this.resize_pie();
		else
			screen_coords = this.resize_chart();
		
		if( this.menu != null )
			this.menu.resize();
		
		// tell the web page that we have resized our content
		if( Reflect.field(chart_parameters,'id') )
			ExternalInterface.call("ofc_resize", screen_coords.left, screen_coords.width, screen_coords.top, screen_coords.height, Reflect.field(chart_parameters,'id'));
		else
			ExternalInterface.call("ofc_resize", screen_coords.left, screen_coords.width, screen_coords.top, screen_coords.height);
			
		screen_coords = null;
	}
		
	function resize_chart(): ScreenCoordsBase {
		//
		// we want to show the tooltip closest to
		// items near the mouse, so hook Into the
		// mouse move event:
		//
		this.addEventListener(MouseEvent.MOUSE_MOVE, this.mouseMove);

		// FlashConnect.trace("stageWidth: " + stage.stageWidth + " stageHeight: " + stage.stageHeight);
		this.background.resize();
		Main.title.resize();
		
		var left: Float   = this.y_legend.get_width() /*+ this.y_labels.get_width()*/ + this.y_axis.get_width();
		
		this.keys.resize( left, Main.title.get_height() );
			
		var top: Float = Main.title.get_height() + this.keys.get_height();
		
		var bottom: Float = this.stage.stageHeight;
		bottom -= (this.x_legend.get_height() + this.x_axis.get_height());
		
		var right: Float = this.stage.stageWidth;
		right -= this.y_legend_2.get_width();
		//right -= this.y_labels_right.get_width();
		right -= this.y_axis_right.get_width();

		// this object is used in the mouseMove method
		this.screen_coords = new ScreenCoords(
			top, left, right, bottom,
			this.y_axis.get_range(),
			this.y_axis_right.get_range(),
			this.x_axis.get_range(),
			this.x_axis.first_label_width(),
			this.x_axis.last_label_width(),
			false );

		this.screen_coords.set_bar_groups(this.object_collection.groups);
			
		this.x_axis.resize( screen_coords,
			// can we remove this:
			this.stage.stageHeight-(this.x_legend.get_height()+this.x_axis.labels.get_height())	// <-- up from the bottom
			);
		this.y_axis.resize( this.y_legend.get_width(), screen_coords );
		this.y_axis_right.resize( 0, screen_coords );
		this.x_legend.resize( screen_coords );
		this.y_legend.resize();
		this.y_legend_2.resize();
		
		this.object_collection.resize( screen_coords );
		
		return screen_coords;
	}
	
	function mouseOut(event:Event):Void {	
		if( this.tooltip != null )
			this.tooltip.hide();
		
		if( this.object_collection != null )
			this.object_collection.mouse_out();
	}
	
	//
	// an external Interface, used by javascreen_coordsript to
	// pass in a JSON string
	//
	public function load( s:String ):Void {
		parse_json( s );
	}

	//
	// JSON is loaded from an external URL
	//
	function xmlLoaded(event:Event):Void {
		var request : URLRequest = cast(event.target, URLRequest);
		var loader:URLLoader = new URLLoader(event.target);
/*		var loader = URLLoader();
		loader.load(event.target);*/
		this.parse_json( loader.data );
	}
	
	//
	// we have data! parse it and make the chart
	//
	function parse_json( json_string:String ):Void {
		
		// //tr.ace(json_string);
		
		var ok:Bool = false;
		
		try {
			var json:Dynamic = JSON.decode( json_string );
			ok = true;
		}
		catch (e:Dynamic) { // NOTE : There is no equivalent as3 Error type in HaXe
			// remove the 'loading data...' msg:
			this.removeChildAt(0);
			this.addChild( new JsonErrorMsg( cast(json_string,String), e ) );
		}
		
		//
		// don't catch these errors:
		//
		if( ok )
		{
			// remove 'loading data...' msg:
			this.removeChildAt(0);
			this.build_chart( json_string );
			
			// force this to be garbage collected
			json_string = null;
		}
		
		json_string = '';
	}
	
	function build_chart( json:Dynamic ):Void {
		
		//tr.ace('----');
		//tr.ace(JSON.encode(json));
		//tr.ace('----');
		
		if ( this.object_collection != null )
			this.die();
		
		// init singletons:
		NumberFormat.getInstance( json );
		NumberFormat.getInstanceY2( json );

		this.tooltip = new Tooltip( json.tooltip );

		var g:Global = Global.getInstance();
		g.set_tooltip_string( this.tooltip.tip_text );
	
		//
		// these are common to both X Y charts and PIE charts:
		this.background	= new Background( json );
		Main.title = new Title( json.title );
		//
		this.addChild( this.background );
		//
		
		if ( JsonInspector.is_radar( json ) ) {
			
			this.object_collection = Factory.makeChart( json );
			this.radar_axis = new RadarAxis( json.radar_axis );
			this.keys = new Keys( this.object_collection );
			
			this.addChild( this.radar_axis );
			this.addChild( this.keys );
			
		}
		else if ( !JsonInspector.has_pie_chart( json ) )
		{
			this.build_chart_background( json );
		}
		else
		{
			// this is a PIE chart
			this.object_collection = Factory.makeChart( json );
			// PIE charts default to FOLLOW tooltips
			this.tooltip.set_tip_style( Tooltip.NORMAL );
		}

		// these are added in the Flash Z Axis order
		this.addChild( Main.title );
		for ( set in this.object_collection.sets )
			this.addChild( set );
		this.addChild( this.tooltip );

		if (Reflect.hasField(json,'menu')) {
			this.menu = new Menu('99', Reflect.field(json,'menu'));
			this.addChild(this.menu);
		}
		
		this.ok = true;
		this.resize();
		
		
	}
	
	//
	// PIE charts don't have this.
	// build grid, axis, legends and key
	//
	function build_chart_background( json:Dynamic ):Void {
		//
		// This reads all the 'elements' of the chart
		// e.g. bars and lines, then creates them as sprites
		//
		this.object_collection = Factory.makeChart( json );
		//
		this.x_legend = new XLegend( json.x_legend );			
		this.y_legend = new YLegendLeft( json );
		this.y_legend_2 = new YLegendRight( json );
		this.x_axis = new XAxis( json, this.object_collection.get_min_x(), this.object_collection.get_max_x() );
		this.y_axis = new YAxisLeft( json );
		this.y_axis_right = new YAxisRight( json );
		
		// access all our globals through this:
		var g:Global = Global.getInstance();
		// this is needed by all the elements tooltip
		g.x_labels = this.x_axis.labels;
		g.x_legend = this.x_legend;

		//  can pick up X Axis labels for the
		// tooltips
		this.object_collection.tooltip_replace_labels( this.x_axis.labels );
		
		this.keys = new Keys( this.object_collection );
		
		this.addChild( this.x_legend );
		this.addChild( this.y_legend );
		this.addChild( this.y_legend_2 );
		this.addChild( this.y_axis );
		this.addChild( this.y_axis_right );
		this.addChild( this.x_axis );
		this.addChild( this.keys );
	}
	
	/**
		* Remove all our referenced objects
		*/
		function die():Void {
		this.object_collection.die();
		this.object_collection = null;
		
		if ( this.tooltip != null ) this.tooltip.die();
		
		if ( this.x_legend != null )	this.x_legend.die();
		if ( this.y_legend != null )	this.y_legend.die();
		if ( this.y_legend_2 != null )	this.y_legend_2.die();
		if ( this.y_axis != null )		this.y_axis.die();
		if ( this.y_axis_right != null ) this.y_axis_right.die();
		if ( this.x_axis != null )		this.x_axis.die();
		if ( this.keys != null )		this.keys.die();
		if ( Main.title != null )		Main.title.die();
		if ( this.radar_axis != null )	this.radar_axis.die();
		if ( this.background != null )	this.background.die();
		
		this.tooltip = null;
		this.x_legend = null;
		this.y_legend = null;
		this.y_legend_2 = null;
		this.y_axis = null;
		this.y_axis_right = null;
		this.x_axis = null;
		this.keys = null;
		Main.title = null;
		this.radar_axis = null;
		this.background = null;
		
		while ( this.numChildren > 0 )
			this.removeChildAt(0);
	
		if ( this.hasEventListener(MouseEvent.MOUSE_MOVE))
			this.removeEventListener(MouseEvent.MOUSE_MOVE, this.mouseMove);
		
		// do not force a garbage collection, it is not supported:
		// http://stackoverflow.com/questions/192373/force-garbage-collectionclickn-as3
	
	}

	function build_right_click_menu(): Void {
	
		var cm = new ContextMenu();
		cm.addEventListener(ContextMenuEvent.MENU_SELECT, onContextMenuHandler);
		cm.hideBuiltInItems();

		// OFC CREDITS
		var fs = new ContextMenuItem("Charts by Open Flash Chart [Version "+VERSION+"]" );
		fs.addEventListener(
			ContextMenuEvent.MENU_ITEM_SELECT,
			function (e:ContextMenuEvent):Void {
				var url:String = "http://teethgrinder.co.uk/open-flash-chart-2/";
				var request = new URLRequest(url);
				// flash.net.navigateToURL(request, '_blank');
				var target : String = "_blank";
#if flash8
				flash.Lib.getURL (url, target); // test target?
#elseif flash9
				flash.Lib.getURL (request, target); // active target
#else
				flash.Lib.getURL (request, target); // and beyond...
#end 
			}
		);
		cm.customItems.push( fs );
		
		var save_image_message:String = if (Reflect.hasField(chart_parameters,'save_image_message'))  Reflect.field(chart_parameters,'save_image_message') else 'Save Image Locally';
		
		var dl:ContextMenuItem = new ContextMenuItem(save_image_message);
		dl.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, this.saveImage);
		cm.customItems.push( dl );
		
		this.contextMenu = cm;
	}
	
	public function format_y_axis_label( val: Float ): String {
	//	if( this._y_format != null )
	//	{
	//		var tmp:String = _root._y_StringTools.replace(format,'#val#',_root.format(val));
	//		tmp = StringTools.replace(tmp,'#val:time#',_root.formatTime(val));
	//		tmp = StringTools.replace(tmp,'#val:none#',String(val));
	//		tmp = StringTools.replace(tmp,'#val:number#', NumberUtils.formatNumber (Number(val)));
	//		return tmp;
	//	}
	//	else
			return NumberUtils.format(val,2,true,true,false);
	}
}

